#include <stdio.h>
#define __STDC_CONSTANT_MACRS

#ifdef _WIN32
// Windows
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <SDL2/SDL.h>
}
#else
// Linux
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <SDL2/SDL.h>
#ifdef __cpluspluc
}
#endif
#endif

#define OUTPUT_YUV420P 0

#define SFM_REFRESH_EVENT (SDL_USEREVENT+1)
#define SFM_BREAK_EVENT (SDL_USEREVENT+2)
int thread_exit = 0;
int thread_pause = 0;

int sfp_refresh_thread(void* opaque) {
	thread_exit = 0;
	thread_pause = 0;
	while (!thread_exit) {
		if (!thread_pause) {
			SDL_Event event;
			event.type = SFM_REFRESH_EVENT;
			SDL_PushEvent(&event);
		}
		SDL_Delay(40);
	}
	thread_exit = 0;
	thread_pause = 0;
	SDL_Event event;
	event.type = SFM_BREAK_EVENT;
	SDL_PushEvent(&event);
	return 0;
}

int main(int argn, char* argv[])
{
	// FFMpeg
	AVFormatContext* pFormatCtx;
	AVCodecContext* pCodecCtx;
	AVCodec* pCodec;
	AVFrame* pFrame;
	AVFrame* pFrameYUV;
	AVPacket* pPacket;
	struct SwsContext* img_convert_ctx;
	int i;
	int videoStream;
	unsigned char* out_buffer;
	int y_size;
	int ret, got_picture;
	char url[] = "http://cctvalih5ca.v.myalicdn.com/live/cctv1_2/index.m3u8";

	// SDL
	int screen_w = 0, screen_h = 0;
	SDL_Window* screen;
	SDL_Renderer* sdlRender;
	SDL_Texture* sdlTexture;
	SDL_Rect sdlRect;
	SDL_Thread* video_tid;
	SDL_Event event;

	// YUV File
	FILE* fp_yuv;



	// av_register_all();
	avformat_network_init();
	pFormatCtx = avformat_alloc_context();
	if (avformat_open_input(&pFormatCtx, url, NULL, NULL) != 0) 
	{
		printf("cannot open input stream.\n");
		return -1;
	}
	if (avformat_find_stream_info(pFormatCtx, NULL) < 0) 
	{
		printf("cannot find valid stream.\n");
		return -1;
	}
	videoStream = -1;
	for (i = 0; i < pFormatCtx->nb_streams; i++) 
	{
		if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) 
		{
			videoStream = i;
			break;
		}
	}
	if (videoStream == -1) 
	{
		printf("cannot find valid video stream.\n");
		return -1;
	}



	pCodecCtx = avcodec_alloc_context3(NULL);
	avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar);
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	if (pCodec == NULL) {
		printf("cannot find valid video codec.\n");
		return -1;
	}
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
		printf("cannot open the codec.\n");
		return -1;
	}



	pFrame = av_frame_alloc();
	pFrameYUV = av_frame_alloc();
	out_buffer = (unsigned char*)av_malloc(
		av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1)
	);
	av_image_fill_arrays(
		pFrameYUV->data, pFrameYUV->linesize,
		out_buffer,
		AV_PIX_FMT_YUV420P,
		pCodecCtx->width, pCodecCtx->height, 1
	);



	pPacket = (AVPacket*)av_malloc(sizeof(AVPacket));



	printf("----- File Information -----\n");
	av_dump_format(pFormatCtx, 0, url, 0);
	printf("----------------------------\n");


	img_convert_ctx = sws_getContext(
		pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
		pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
		SWS_BICUBIC,
		NULL, NULL, NULL
	);



#if OUTPUT_YUV420P
	fp_yuv = fopen("output.yuv", "wb+");
#endif




	if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_TIMER)) {
		printf("cannot initialize SDL - %s \n", SDL_GetError());
		return -1;
	}
	screen_w = pCodecCtx->width;
	screen_h = pCodecCtx->height;
	screen = SDL_CreateWindow(
		"FFPlayer",
		SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h,
		SDL_WINDOW_OPENGL
	);
	if (!screen) {
		printf("SDL: cannot create widnow - existing:%s\n", SDL_GetError());
	}
	sdlRender = SDL_CreateRenderer(screen, -1, 0);
	sdlTexture = SDL_CreateTexture(sdlRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
	sdlRect.x = 0;
	sdlRect.y = 0;
	sdlRect.w = screen_w;
	sdlRect.h = screen_h;
	video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);

	for (;;) {
		SDL_WaitEvent(&event);
		if (event.type == SFM_REFRESH_EVENT) {
			while (1) {
				if (av_read_frame(pFormatCtx, pPacket) < 0) thread_exit = 1;
				if (pPacket->stream_index == videoStream) break;
			}
			ret = avcodec_send_packet(pCodecCtx, pPacket);
			if (ret < 0)
			{
				printf("send packet error.\n");
				return -1;
			}
			while (ret >= 0)
			{
				ret = avcodec_receive_frame(pCodecCtx, pFrame);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
				{
					break;
				}
				else if (ret < 0)
				{
					printf("receive frame error.\n");
					return -1;
				}
				else
				{
					sws_scale(
						img_convert_ctx,
						(const unsigned char* const*)pFrame->data, pFrame->linesize,
						0, pCodecCtx->height,
						pFrameYUV->data, pFrameYUV->linesize
					);
#if OUTPUT_YUV420P
					y_size = pCodecCtx->width * pCodecCtx->height;
					fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);
					fwrite(pFrameYUV->data[0], 1, y_size / 4, fp_yuv);
					fwrite(pFrameYUV->data[0], 1, y_size / 4, fp_yuv);
#endif
					SDL_UpdateYUVTexture(
						sdlTexture, &sdlRect,
						pFrameYUV->data[0], pFrameYUV->linesize[0],
						pFrameYUV->data[1], pFrameYUV->linesize[1],
						pFrameYUV->data[2], pFrameYUV->linesize[2]
					);
					SDL_RenderClear(sdlRender);
					SDL_RenderCopy(sdlRender, sdlTexture, NULL, &sdlRect);
					SDL_RenderPresent(sdlRender);
				}
			}

		}
		else if (event.type == SDL_KEYDOWN) {
			if (event.key.keysym.sym == SDLK_SPACE) thread_pause = !thread_pause;
		}
		else if (event.type == SDL_QUIT) {
			thread_exit = 1;
		}
		else if (event.type == SFM_BREAK_EVENT) {
			break;
		}
	}

	/*
	while (av_read_frame(pFormatCtx, pPacket) >= 0)
	{
		if (pPacket->stream_index == videoStream)
		{
			ret = avcodec_send_packet(pCodecCtx, pPacket);
			if (ret < 0)
			{
				printf("send packet error.\n");
				return -1;
			}
			while (ret >= 0)
			{
				ret = avcodec_receive_frame(pCodecCtx, pFrame);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
				{
					break;
				}
				else if (ret < 0)
				{
					printf("receive frame error.\n");
					return -1;
				}
				else
				{
					sws_scale(
						img_convert_ctx,
						(const unsigned char* const*)pFrame->data, pFrame->linesize,
						0, pCodecCtx->height,
						pFrameYUV->data, pFrameYUV->linesize
					);
#if OUTPUT_YUV420P
					y_size = pCodecCtx->width * pCodecCtx->height;
					fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);
					fwrite(pFrameYUV->data[0], 1, y_size/4, fp_yuv);
					fwrite(pFrameYUV->data[0], 1, y_size/4, fp_yuv);
#endif
					SDL_UpdateYUVTexture(
						sdlTexture, &sdlRect,
						pFrameYUV->data[0], pFrameYUV->linesize[0],
						pFrameYUV->data[1], pFrameYUV->linesize[1],
						pFrameYUV->data[2], pFrameYUV->linesize[2]
					);
					SDL_RenderClear(sdlRender);
					SDL_RenderCopy(sdlRender, sdlTexture, NULL, &sdlRect);
					SDL_RenderPresent(sdlRender);
					SDL_Delay(40);
				}
			}
		}
	}

	for (;;) {
		ret = avcodec_send_packet(pCodecCtx, pPacket);
		if (ret < 0)
		{
			printf("send packet error.\n");
			return -1;
		}
		while (ret >= 0)
		{
			ret = avcodec_receive_frame(pCodecCtx, pFrame);
			if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			{
				break;
			}
			else if (ret < 0)
			{
				printf("receive frame error.\n");
				return -1;
			}
			else
			{
				sws_scale(
					img_convert_ctx,
					(const unsigned char* const*)pFrame->data, pFrame->linesize,
					0, pCodecCtx->height,
					pFrameYUV->data, pFrameYUV->linesize
				);
#if OUTPUT_YUV420P
				y_size = pCodecCtx->width * pCodecCtx->height;
				fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);
				fwrite(pFrameYUV->data[0], 1, y_size / 4, fp_yuv);
				fwrite(pFrameYUV->data[0], 1, y_size / 4, fp_yuv);
#endif
				SDL_UpdateYUVTexture(
					sdlTexture, &sdlRect,
					pFrameYUV->data[0], pFrameYUV->linesize[0],
					pFrameYUV->data[1], pFrameYUV->linesize[1],
					pFrameYUV->data[2], pFrameYUV->linesize[2]
				);
				SDL_RenderClear(sdlRender);
				SDL_RenderCopy(sdlRender, sdlTexture, NULL, &sdlRect);
				SDL_RenderPresent(sdlRender);
				SDL_Delay(40);
			}
		}
	}
	*/

	sws_freeContext(img_convert_ctx);
#if OUTPUT_YUV420P
	fclose(fp_yuv);
#endif
	SDL_Quit();
	av_frame_free(&pFrameYUV);
	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);

	return 0;
}